<template>
	<view class="uni-file-picker">
		<view v-if="title" class="uni-file-picker__header">
			<text class="file-title">{{ title }}</text>
			<text class="file-count">{{ filesList.length }}/{{ limitLength }}</text>
		</view>
		<view v-if="subtitle" class="file-subtitle">
			<view>{{ subtitle }}</view>
		</view>
		<upload-image
			v-if="fileMediatype === 'image' && showType === 'grid'"
			:readonly="readonly"
			:image-styles="imageStyles"
			:files-list="url"
			:limit="limitLength"
			:disablePreview="disablePreview"
			:delIcon="delIcon"
			@uploadFiles="uploadFiles"
			@choose="choose"
			@delFile="delFile"
		>
			<slot>
				<view class="is-add">
					<image :src="imgsrc" class="add-icon"></image>
				</view>
			</slot>
		</upload-image>
		<upload-file
			v-if="fileMediatype !== 'image' || showType !== 'grid'"
			:readonly="readonly"
			:list-styles="listStyles"
			:files-list="filesList"
			:showType="showType"
			:delIcon="delIcon"
			@uploadFiles="uploadFiles"
			@choose="choose"
			@delFile="delFile"
		>
			<slot><button type="primary" size="mini">选择文件</button></slot>
		</upload-file>
	</view>
</template>

<script>
		import { chooseAndUploadFile, uploadCloudFiles } from './choose-and-upload-file.js';
		import {
			get_file_ext,
			get_extname,
			get_files_and_is_max,
			get_file_info,
			get_file_data,
		} from './utils.js';
		import uploadImage from './upload-image.vue';
		import uploadFile from './upload-file.vue';
		import $url from '/shop/url/index.js'
		let fileInput = null;
		/**
		 * FilePicker 文件选择上传
		 * @description 文件选择上传组件，可以选择图片、视频等任意文件并上传到当前绑定的服务空间
		 * @tutorial https://ext.dcloud.net.cn/plugin?id=4079
		 * @property {Object|Array}	value	组件数据，通常用来回显 ,类型由return-type属性决定
		 * @property {String|Array}	url	  url数据
		 * @property {Boolean}	disabled = [true|false]	组件禁用
		 * 	@value true 	禁用
		 * 	@value false 	取消禁用
		 * @property {Boolean}	readonly = [true|false]	组件只读，不可选择，不显示进度，不显示删除按钮
		 * 	@value true 	只读
		 * 	@value false 	取消只读
		 * @property {Boolean}	disable-preview = [true|false]	禁用图片预览，仅 mode:grid 时生效
		 * 	@value true 	禁用图片预览
		 * 	@value false 	取消禁用图片预览
		 * @property {Boolean}	del-icon = [true|false]	是否显示删除按钮
		 * 	@value true 	显示删除按钮
		 * 	@value false 	不显示删除按钮
		 * @property {Boolean}	auto-upload = [true|false]	是否自动上传，值为true则只触发@select,可自行上传
		 * 	@value true 	自动上传
		 * 	@value false 	取消自动上传
		 * @property {Number|String}	limit	最大选择个数 ，h5 会自动忽略多选的部分
		 * @property {String}	title	组件标题，右侧显示上传计数
		 * @property {String}	mode = [list|grid]	选择文件后的文件列表样式
		 * 	@value list 	列表显示
		 * 	@value grid 	宫格显示
		 * @property {String}	file-mediatype = [image|video|all]	选择文件类型
		 * 	@value image	只选择图片
		 * 	@value video	只选择视频
		 * 	@value all		选择所有文件
		 * @property {Array}	file-extname	选择文件后缀，根据 file-mediatype 属性而不同
		 * @property {Object}	list-style	mode:list 时的样式
		 * @property {Object}	image-styles	选择文件后缀，根据 file-mediatype 属性而不同
		 * @event {Function} select 	选择文件后触发
		 * @event {Function} progress 文件上传时触发
		 * @event {Function} success 	上传成功触发
		 * @event {Function} fail 		上传失败触发
		 * @event {Function} delete 	文件从列表移除时触发
		 */
		export default {
			name: 'sUploader',
			components: {
				uploadImage,
				uploadFile,
			},
			options: {
				virtualHost: true,
			},
			emits: [
				'select',
				'success',
				'fail',
				'progress',
				'delete',
				'update:modelValue',
				'update:url',
			],
			props: {
				modelValue: {
					type: [Array, Object],
					default() {
						return [];
					},
				},
				url: {
					type: [Array, String],
					default() {
						return [];
					},
				},
				disabled: {
					type: Boolean,
					default: false,
				},
				disablePreview: {
					type: Boolean,
					default: false,
				},
				delIcon: {
					type: Boolean,
					default: true,
				},
				// 自动上传
				autoUpload: {
					type: Boolean,
					default: true,
				},
				// 最大选择个数 ，h5只能限制单选或是多选
				limit: {
					type: [Number, String],
					default: 9,
				},
				// 列表样式 grid | list | list-card
				mode: {
					type: String,
					default: 'grid',
				},
				// 选择文件类型  image/video/all
				fileMediatype: {
					type: String,
					default: 'image',
				},
				// 文件类型筛选
				fileExtname: {
					type: [Array, String],
					default() {
						return [];
					},
				},
				title: {
					type: String,
					default: '',
				},
				listStyles: {
					type: Object,
					default() {
						return {
							// 是否显示边框
							border: true,
							// 是否显示分隔线
							dividline: true,
							// 线条样式
							borderStyle: {},
						};
					},
				},
				imageStyles: {
					type: Object,
					default() {
						return {
							width: 'auto',
							height: 'auto',
						};
					},
				},
				readonly: {
					type: Boolean,
					default: false,
				},
				sizeType: {
					type: Array,
					default() {
						return ['original', 'compressed'];
					},
				},
				driver: {
					type: String,
					default: 'local', // local=本地 | oss | unicloud
				},
				subtitle: {
					type: String,
					default: '',
				},
			},
			data() {
				return {
					files: [],
					localValue: [],
					imgsrc: $url.static('/static/img/shop/upload-camera.png'),
				};
			},
			watch: {
				modelValue: {
					handler(newVal, oldVal) {
						this.setValue(newVal, oldVal);
					},
					immediate: true,
				},
			},
			computed: {
				returnType() {
					if (this.limit > 1) {
						return 'array';
					}
					return 'object';
				},
				filesList() {
					let files = [];
					this.files.forEach((v) => {
						files.push(v);
					});
					return files;
				},
				showType() {
					if (this.fileMediatype === 'image') {
						return this.mode;
					}
					return 'list';
				},
				limitLength() {
					if (this.returnType === 'object') {
						return 1;
					}
					if (!this.limit) {
						return 1;
					}
					if (this.limit >= 9) {
						return 9;
					}
					return this.limit;
				},
			},
			created() {
				if (this.driver === 'local') {
					uniCloud.chooseAndUploadFile = chooseAndUploadFile;
				}
				this.form = this.getForm('uniForms');
				this.formItem = this.getForm('uniFormsItem');
				if (this.form && this.formItem) {
					if (this.formItem.name) {
						this.rename = this.formItem.name;
						this.form.inputChildrens.push(this);
					}
				}
			},
			methods: {
				/**
				 * 公开用户使用，清空文件
				 * @param {Object} index
				 */
				clearFiles(index) {
					if (index !== 0 && !index) {
						this.files = [];
						this.$nextTick(() => {
							this.setEmit();
						});
					} else {
						this.files.splice(index, 1);
					}
					this.$nextTick(() => {
						this.setEmit();
					});
				},
				/**
				 * 公开用户使用，继续上传
				 */
				upload() {
					let files = [];
					this.files.forEach((v, index) => {
						if (v.status === 'ready' || v.status === 'error') {
							files.push(Object.assign({}, v));
						}
					});
					return this.uploadFiles(files);
				},
				async setValue(newVal, oldVal) {
					const newData = async (v) => {
						const reg = /cloud:\/\/([\w.]+\/?)\S*/;
						let url = '';
						if (v.fileID) {
							url = v.fileID;
						} else {
							url = v.url;
						}
						if (reg.test(url)) {
							v.fileID = url;
							v.url = await this.getTempFileURL(url);
						}
						if (v.url) v.path = v.url;
						return v;
					};
					if (this.returnType === 'object') {
						if (newVal) {
							await newData(newVal);
						} else {
							newVal = {};
						}
					} else {
						if (!newVal) newVal = [];
						for (let i = 0; i < newVal.length; i++) {
							let v = newVal[i];
							await newData(v);
						}
					}
					this.localValue = newVal;
					if (this.form && this.formItem && !this.is_reset) {
						this.is_reset = false;
						this.formItem.setValue(this.localValue);
					}
					let filesData = Object.keys(newVal).length > 0 ? newVal : [];
					this.files = [].concat(filesData);
				},

				/**
				 * 选择文件
				 */
				choose() {
					if (this.disabled) return;
					if (
						this.files.length >= Number(this.limitLength) &&
						this.showType !== 'grid' &&
						this.returnType === 'array'
					) {
						uni.showToast({
							title: `您最多选择 ${this.limitLength} 个文件`,
							icon: 'none',
						});
						return;
					}
					this.chooseFiles();
				},

				/**
				 * 选择文件并上传
				 */
				chooseFiles() {
					const _extname = get_extname(this.fileExtname);
					// 获取后缀
					uniCloud
						.chooseAndUploadFile({
							type: this.fileMediatype,
							compressed: false,
							sizeType: this.sizeType,
							// TODO 如果为空，video 有问题
							extension: _extname.length > 0 ? _extname : undefined,
							count: this.limitLength - this.files.length, //默认9
							onChooseFile: this.chooseFileCallback,
							onUploadProgress: (progressEvent) => {
								this.setProgress(progressEvent, progressEvent.index);
							},
						})
						.then((result) => {
							this.setSuccessAndError(result.tempFiles);
						})
						.catch((err) => {
							console.log('选择失败', err);
						});
				},

				/**
				 * 选择文件回调
				 * @param {Object} res
				 */
				async chooseFileCallback(res) {
					const _extname = get_extname(this.fileExtname);
					const is_one =
						(Number(this.limitLength) === 1 && this.disablePreview && !this.disabled) ||
						this.returnType === 'object';
					// 如果这有一个文件 ，需要清空本地缓存数据
					if (is_one) {
						this.files = [];
					}

					let { filePaths, files } = get_files_and_is_max(res, _extname);
					if (!(_extname && _extname.length > 0)) {
						filePaths = res.tempFilePaths;
						files = res.tempFiles;
					}

					let currentData = [];
					for (let i = 0; i < files.length; i++) {
						if (this.limitLength - this.files.length <= 0) break;
						files[i].uuid = Date.now();
						let filedata = await get_file_data(files[i], this.fileMediatype);
						filedata.progress = 0;
						filedata.status = 'ready';
						this.files.push(filedata);
						currentData.push({
							...filedata,
							file: files[i],
						});
					}
					this.$emit('select', {
						tempFiles: currentData,
						tempFilePaths: filePaths,
					});
					res.tempFiles = files;
					// 停止自动上传
					if (!this.autoUpload) {
						res.tempFiles = [];
					}
				},

				/**
				 * 批传
				 * @param {Object} e
				 */
				uploadFiles(files) {
					files = [].concat(files);
					return uploadCloudFiles
						.call(this, files, 5, (res) => {
							this.setProgress(res, res.index, true);
						})
						.then((result) => {
							this.setSuccessAndError(result);
							return result;
						})
						.catch((err) => {
							console.log(err);
						});
				},

				/**
				 * 成功或失败
				 */
				async setSuccessAndError(res, fn) {
					let successData = [];
					let errorData = [];
					let tempFilePath = [];
					let errorTempFilePath = [];
					for (let i = 0; i < res.length; i++) {
						const item = res[i];
						const index = item.uuid
							? this.files.findIndex((p) => p.uuid === item.uuid)
							: item.index;

						if (index === -1 || !this.files) break;
						if (item.errMsg === 'request:fail') {
							this.files[index].url = item.path;
							this.files[index].status = 'error';
							this.files[index].errMsg = item.errMsg;
							// this.files[index].progress = -1
							errorData.push(this.files[index]);
							errorTempFilePath.push(this.files[index].url);
						} else {
							this.files[index].errMsg = '';
							this.files[index].fileID = item.url;
							const reg = /cloud:\/\/([\w.]+\/?)\S*/;
							if (reg.test(item.url)) {
								this.files[index].url = await this.getTempFileURL(item.url);
							} else {
								this.files[index].url = item.url;
							}

							this.files[index].status = 'success';
							this.files[index].progress += 1;
							successData.push(this.files[index]);
							tempFilePath.push(this.files[index].fileID);
						}
					}

					if (successData.length > 0) {
						this.setEmit();
						// 状态改变返回
						this.$emit('success', {
							tempFiles: this.backObject(successData),
							tempFilePaths: tempFilePath,
						});
					}

					if (errorData.length > 0) {
						this.$emit('fail', {
							tempFiles: this.backObject(errorData),
							tempFilePaths: errorTempFilePath,
						});
					}
				},

				/**
				 * 获取进度
				 * @param {Object} progressEvent
				 * @param {Object} index
				 * @param {Object} type
				 */
				setProgress(progressEvent, index, type) {
					const fileLenth = this.files.length;
					const percentNum = (index / fileLenth) * 100;
					const percentCompleted = Math.round(
						(progressEvent.loaded * 100) / progressEvent.total,
					);
					let idx = index;
					if (!type) {
						idx = this.files.findIndex((p) => p.uuid === progressEvent.tempFile.uuid);
					}
					if (idx === -1 || !this.files[idx]) return;
					// fix by mehaotian 100 就会消失，-1 是为了让进度条消失
					this.files[idx].progress = percentCompleted - 1;
					// 上传中
					this.$emit('progress', {
						index: idx,
						progress: parseInt(percentCompleted),
						tempFile: this.files[idx],
					});
				},

				/**
				 * 删除文件
				 * @param {Object} index
				 */
				delFile(index) {
					this.$emit('delete', {
						tempFile: this.files[index],
						tempFilePath: this.files[index].url,
					});
					this.files.splice(index, 1);
					this.$nextTick(() => {
						this.setEmit();
					});
				},

				/**
				 * 获取文件名和后缀
				 * @param {Object} name
				 */
				getFileExt(name) {
					const last_len = name.lastIndexOf('.');
					const len = name.length;
					return {
						name: name.substring(0, last_len),
						ext: name.substring(last_len + 1, len),
					};
				},

				/**
				 * 处理返回事件
				 */
				setEmit() {
					let data = [];
					let updateUrl = [];
					if (this.returnType === 'object') {
						data = this.backObject(this.files)[0];
						this.localValue = data ? data : null;
						updateUrl = data ? data.url : '';
					} else {
						data = this.backObject(this.files);
						if (!this.localValue) {
							this.localValue = [];
						}
						this.localValue = [...data];
						if (this.localValue.length > 0) {
							this.localValue.forEach((item) => {
								updateUrl.push(item.url);
							});
						}
					}
					this.$emit('update:modelValue', this.localValue);
					this.$emit('update:url', updateUrl);
				},

				/**
				 * 处理返回参数
				 * @param {Object} files
				 */
				backObject(files) {
					let newFilesData = [];
					files.forEach((v) => {
						newFilesData.push({
							extname: v.extname,
							fileType: v.fileType,
							image: v.image,
							name: v.name,
							path: v.path,
							size: v.size,
							fileID: v.fileID,
							url: v.url,
						});
					});
					return newFilesData;
				},
				async getTempFileURL(fileList) {
					fileList = {
						fileList: [].concat(fileList),
					};
					const urls = await uniCloud.getTempFileURL(fileList);
					return urls.fileList[0].tempFileURL || '';
				},
				/**
				 * 获取父元素实例
				 */
				getForm(name = 'uniForms') {
					let parent = this.$parent;
					let parentName = parent.$options.name;
					while (parentName !== name) {
						parent = parent.$parent;
						if (!parent) return false;
						parentName = parent.$options.name;
					}
					return parent;
				},
			},
		};
</script>

<style lang="scss" scoped>
	.uni-file-picker {
		/* #ifndef APP-NVUE */
		box-sizing: border-box;
		overflow: hidden;
		/* width: 100%; */
		/* #endif */
		/* flex: 1; */
		position: relative;
	}

	.uni-file-picker__header {
		padding-top: 5px;
		padding-bottom: 10px;
		/* #ifndef APP-NVUE */
		display: flex;
		/* #endif */
		justify-content: space-between;
	}

	.file-title {
		font-size: 14px;
		color: #333;
	}

	.file-count {
		font-size: 14px;
		color: #999;
	}

	.is-add {
		/* #ifndef APP-NVUE */
		display: flex;
		/* #endif */
		align-items: center;
		justify-content: center;
	}
	.add-icon {
		width: 57rpx;
		height: 49rpx;
	}
	.file-subtitle {
		position: absolute;
		left: 50%;
		transform: translateX(-50%);
		bottom: 0;
		width: 140rpx;
		height: 36rpx;
		z-index: 1;
		display: flex;
		justify-content: center;
		color: #fff;
		font-weight: 500;
		background: rgba(#000, 0.3);
		font-size: 24rpx;
	}
</style>
